namespace RL

open Raylib_CsLo
open Garnet.Composition
open RL.Drawing
open RL.Mapping
open RL.MapGeneration
open RL.Random
open System

module Game =
    [<Literal>]
    let WindowWidth = 1280
    [<Literal>]
    let WindowHeight = 800
    [<Literal>]
    let WindowTitle = "Roguelike Tutorial 2022 [Week 2]"

    let buildWorld(world : Container, rng : RandomNumberGenerator) =
        // Create the player entity, preserving its Eid.
        let player = EntityFactory.Player world

        // Create some NPCs and get a list of their Eids.
        let enemies =
            seq { 1 .. 11 }
            |> Seq.map (fun _ ->
                        match rng.Next(0, 2) with
                            | 0 -> EntityFactory.Orc world
                            | _ -> EntityFactory.Troll world)
            |> Seq.map (fun e -> e)
            |> Seq.toList
        world.Commit()  // Commit all new entities to the ECS.

        let _, map : bool * RLMap = world.TryGetResource("Map")               // Get the map resource back from the ECS.
        let rooms = map.GenerateBasicRooms(rng, 12, 5, 12)                    // Run the map generator.
        LevelGeneration.PlaceInRoom(world, rng, player, rooms[0],             // Place the player in the center of the first room.
                                    LevelGeneration.RoomPlacement.Center)
        let mutable unusedRooms = seq { 1..rooms.Length - 1 } |> Seq.toList    // Preserve a list of unused rooms. (Room 0 has already been used.)
        for eid in enemies do                                                 // Iterate each enemy Eid.
            let roomSelect = rng.Next(0, unusedRooms.Length)                 // Select an unused room at random.
            LevelGeneration.PlaceInRoom(world, rng, eid,                     // Place the enemy at a random spot in the selected room.
                                        rooms[unusedRooms[roomSelect]],
                                        LevelGeneration.RoomPlacement.Random)
            unusedRooms <- unusedRooms |> List.removeAt roomSelect             // Get rid of the selected room in the unused rooms list.


    let initGame =
        let world = Container() // Set up a new ECS.
        Input.init(world)       // Initialize the input system.
        Drawing.init(world)     // Initialize the drawing system.
        FieldOfVision.init(world)
        Action.init(world)
        AI.init(world)

        // Initialize a new Raylib window.
        Raylib.InitWindow(WindowWidth, WindowHeight, WindowTitle)
        Raylib.SetTargetFPS(60) // Lock to 60 FPS.
        Raylib.SetExitKey(0)    // Unbind ESC as the exit key.

        let ss = SharedState("assets/font.png", // Initialize the shared state after Raylib has been initialized.
                             16, 16,
                             80, 50)

        world.AddResource("Map", RLMap(80, 50))     // Add the game map class to the ECS's resources.
        world.AddResource("Draw", DrawCallContainer())

        buildWorld(world, ss.RNG)                           // Build the world.
        world.Run <| FieldOfVision.FOVProcess()             // Process FOV calculations for the first time.
        world.Run <| { PlayerRefresh.s = ss }
        (world, ss)                                         // Return the ECS and shared-state.

    let rec loop(world : Container, ss : SharedState) =
        world.Run <| { Input.InputTrigger.s = ss }
        Raylib.BeginDrawing()                       // Start a drawing block.
        Raylib.ClearBackground(Raylib.BLACK)        // Clear the background to black.

        let _, draw : bool * DrawCallContainer = world.TryGetResource("Draw")    // Get the draw call buffer from the ECS.
        for call in draw.Calls do                   // Iterate any stored drawing calls.
            world.Run <| call                      // Push the calls through to the ECS to trigger refreshes.
        draw.Clear()                                // Clear out the buffer.

        ss.RenderLayers                             // Render the layers to the screen texture.
        ss.DrawScreenTexture                        // Draw the previously rendered screen texture.

        Raylib.DrawFPS(10, 10)                      // Draw the FPS to the screen
        Raylib.EndDrawing()                         // End the drawing block.

        if not ss.QuitFlag then
            loop(world, ss)
        else
            Raylib.CloseWindow()
